package registration.servlet;

import java.net.*;
import java.sql.SQLException;
import java.util.Collection;

import javax.annotation.Resource;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import javax.ws.rs.core.Response.Status;

import msg.*;
import msg.xml.XMLTransformer;

import org.json.*;
import org.w3c.dom.*;

import registration.Consensus;
import user.*;
import user.servlet.UsersResource;

import common.exceptions.*;
import common.extensions.Extender;
import common.servlet.*;

@Resource
@Path("/events")
@SuppressWarnings({"javadoc", "static-method"})
public class EventResource extends AbstractResource {

	@POST
	public Response post(JSONObject object, @CookieParam(value = UsersResource.COOKIE_NAME) Long sessionId) {
		try (ManagersBundle mb = new ManagersBundle()) {
			if (sessionId == null) {
				return missingCookie();
			}
			String content = object.getString("content");
			JSONObject location = object.getJSONObject("location");
			int capacity = object.getInt("capacity");
			long time = object.getLong("time");
			String userId = mb.lm.getSession(sessionId).getUser().getId();
			Location loc = new Location(location);
			Message msg = mb.mm.createNewEventMessage(loc, content, mb.lm.get(userId), time, capacity);
			mb.commit();
			return Response.created(new URI(msg.getId().toString())).build();
		} catch (JSONException e) {
			return handleJsonException(e);
		} catch (InvalidKeyException e) {
			return missingCookie();
		} catch (ArithmeticException e) {
			return handleUserException(e);
		} catch (RuntimeException | URISyntaxException | SQLException e) {
			return handleException(e);
		}
	}

	@GET
	@Path("/{id}")
	@Produces(MediaType.APPLICATION_JSON)
	public Response retrieve(@PathParam("id") long eventId) {
		try (ManagersBundle mb = new ManagersBundle()) {
			EventMessage event = getEvent(eventId, mb);
			JSONObject result = toJSON(event);
			mb.commit();
			return Response.ok().entity(result).build();
		} catch (InvalidKeyException e) {
			return Response.status(Status.NOT_FOUND).build();
		} catch (RuntimeException | SQLException e) {
			return handleException(e);
		}
	}

	@DELETE
	@Path("/{id}")
	public Response delete(@PathParam("id") Long eventId,
			@CookieParam(value = UsersResource.COOKIE_NAME) Long sessionId) {
		try (ManagersBundle mb = new ManagersBundle()) {
			if (sessionId == null) {
				return missingCookie();
			}
			Session session = mb.lm.getSession(sessionId);
			String userId = session.getUser().getId();
			Message msg;
			try {
				msg = mb.mm.get(eventId);
			} catch (InvalidKeyException e) {
				return Response.noContent().build();
			}
			if (msg.getPoster().getId().equals(userId) == false) {
				return Response.status(Status.FORBIDDEN).entity("you are not the creator of this message")
						.build();
			}
			if (isEvent(msg) == false) {
				return Response.status(Status.NOT_FOUND).entity("id belongs to a message").build();
			}
			if (((EventMessage)msg).isOver()) {
				return Response.status(Status.FORBIDDEN).entity("cannot delete old events").build();
			}
			mb.mm.delete(eventId);
			mb.commit();
			return Response.ok().build();
		} catch (InvalidKeyException e) {
			return missingCookie();
		} catch (RuntimeException | SQLException e) {
			return handleException(e);
		}
	}

	@GET
	@Produces(MediaType.APPLICATION_JSON)
	public Response list() {
		try (ManagersBundle mb = new ManagersBundle()) {
			Collection<Message> msgs = mb.mm.getAll();
			mb.commit();
			msgs = Extender.where(msgs, getEventPredicate());
			JSONArray array = msgsToOrderedArray(msgs);
			return Response.ok().entity(array).build();
		} catch (RuntimeException | SQLException e) {
			return handleException(e);
		}
	}

	@GET
	@Path("/{id}/registered")
	@Produces(MediaType.APPLICATION_JSON)
	public Response listRegistered(@PathParam("id") Long eventId) {
		try (ManagersBundle mb = new ManagersBundle()) {
			if (eventId == null) {
				return missingCookie();
			}
			EventMessage event = getEvent(eventId, mb);
			Collection<User> users = mb.rm.getAllRegistrationsToEvent(event);
			JSONArray array = new JSONArray();
			for (User user : users) {
				array.put(userToJSON(user));
			}
			mb.commit();
			return Response.ok().entity(array).build();
		} catch (InvalidKeyException e) {
			return Response.status(Status.NOT_FOUND).build();
		} catch (RuntimeException | SQLException e) {
			return handleException(e);
		}
	}

	@POST
	@Path("/{id}/register")
	public Response register(@PathParam("id") long eventId,
			@CookieParam(value = UsersResource.COOKIE_NAME) Long sessionId) {
		try (ManagersBundle mb = new ManagersBundle()) {
			if (sessionId == null) {
				return missingCookie();
			}

			User user = getUserFromSession(sessionId, mb);
			try {
				EventMessage event = getEvent(eventId, mb);
				if (mb.rm.registerUserForEvent(user, event)) {
					mb.commit();
					return Response.ok().build();
				} else {
					return Response.noContent().build();
				}
			} catch (InvalidKeyException e) {
				return Response.status(Status.NOT_FOUND).build();
			}
		} catch (JSONException e) {
			return handleJsonException(e);
		} catch (InvalidKeyException e) {
			return missingCookie();
		} catch (EventException e) {
			return handleEventException(e);
		} catch (RuntimeException | SQLException e) {
			return handleException(e);
		}
	}

	private Response handleEventException(EventException e) {
		return Response.status(Status.FORBIDDEN).entity(e.getMessage()).build();
	}

	@DELETE
	@Path("/{id}/register")
	public Response unregister(@PathParam("id") long eventId,
			@CookieParam(value = UsersResource.COOKIE_NAME) Long sessionId) {
		try (ManagersBundle mb = new ManagersBundle()) {
			if (sessionId == null) {
				return missingCookie();
			}

			User user = getUserFromSession(sessionId, mb);
			try {
				EventMessage event = getEvent(eventId, mb);
				if (mb.rm.unregisterUserForEvent(user, event)) {
					mb.commit();
					return Response.ok().build();
				} else {
					return Response.noContent().build();
				}
			} catch (InvalidKeyException e) {
				return Response.status(Status.NOT_FOUND).build();
			}
		} catch (InvalidKeyException e) {
			return missingCookie();
		} catch (EventException e) {
			return missingCookie();
		} catch (RuntimeException | SQLException e) {
			return handleException(e);
		}
	}

	@POST
	@Path("/{id}/cons")
	public Response addConsensus(@PathParam("id") long eventId,
			@CookieParam(value = UsersResource.COOKIE_NAME) Long sessionId, JSONObject object) {
		try (ManagersBundle mb = new ManagersBundle()) {
			if (sessionId == null) {
				return missingCookie();
			}
			User user = getUserFromSession(sessionId, mb);
			EventMessage event = getEvent(eventId, mb);
			if (event.getPoster().equals(user) == false) {
				return Response.status(Status.UNAUTHORIZED)
						.entity("only the event poster can create consensuses")
						.build();
			}
			Consensus con = mb.rm.addConsensus(event, object.getString("name"));
			mb.commit();
			return Response.created(new URI(con.getId().toString())).build();
		} catch (JSONException e) {
			return handleJsonException(e);
		} catch (EventException e) {
			return handleEventException(e);
		} catch (InvalidKeyException e) {
			return missingCookie();
		} catch (RuntimeException | URISyntaxException | SQLException e) {
			return handleException(e);
		}
	}

	@GET
	@Path("/{id}/cons")
	@Produces(MediaType.APPLICATION_JSON)
	public Response listCons(@PathParam("id") long eventId,
			@CookieParam(value = UsersResource.COOKIE_NAME) Long sessionId) {
		try (ManagersBundle mb = new ManagersBundle()) {
			EventMessage event = getEvent(eventId, mb);
			JSONArray array = new JSONArray();
			User user = null;
			if (sessionId != null) {
				user = getUserFromSession(sessionId, mb);
			}
			for (Consensus con : mb.rm.getAllEventConsensuses(event)) {
				array.put(conToJson(con, mb, user));
			}
			mb.commit();
			return Response.ok(array).build();
		} catch (InvalidKeyException e) {
			return Response.status(Status.NOT_FOUND).build();
		} catch (RuntimeException | SQLException e) {
			return handleException(e);
		}
	}

	@GET
	@Path("/inRadius")
	@Produces(MediaType.APPLICATION_JSON)
	public Response listInRadius(@QueryParam("lat") double latCenter, @QueryParam("long") double longCenter,
			@QueryParam("n") double n, @QueryParam("s") double s, @QueryParam("w") double w,
			@QueryParam("e") double e, @QueryParam("since") Long since, @QueryParam("past") Boolean past) {
		try (ManagersBundle mb = new ManagersBundle()) {
			Location center = new Location(latCenter, longCenter);
			Location sw = new Location(w, s);
			Location ne = new Location(e, n);
			double distance = getRadius(center, sw, ne);
			Collection<EventMessage> msgs = mb.mm.getAllEventsInRadius(center, distance, since, past);
			mb.commit();
			JSONArray array = msgsToOrderedArray(msgs);
			return Response.ok().entity(array).build();
		} catch (JSONException exp) {
			return handleJsonException(exp);
		} catch (Exception exp) {
			return handleException(exp);
		}
	}

	@GET
	@Path("/xml")
	@Produces(MediaType.TEXT_XML)
	public Response toXml() {
		try (ManagersBundle mb = new ManagersBundle()) {
			Collection<Message> all = mb.mm.getAll();
			mb.commit();
			Document doc = XMLTransformer.toXML(all);
			Node result = XMLTransformer.toEventsOnly(doc);
			return sendXML(result, "events.xml");
		} catch (RuntimeException | SQLException e) {
			return handleException(e);
		}
	}

	@GET
	@Path("/kml")
	@Produces(MediaType.TEXT_XML)
	public Response toKml() {
		try (ManagersBundle mb = new ManagersBundle()) {
			Collection<Message> all = mb.mm.getAll();
			mb.commit();
			Document doc = XMLTransformer.toXML(all);
			Node result = XMLTransformer.toEventsOnly(doc);
			result = XMLTransformer.toKML(result);
			return sendXML(result, "events.kml");
		} catch (RuntimeException | SQLException e) {
			return handleException(e);
		}
	}

	@GET
	@Path("/path")
	@Produces(MediaType.TEXT_XML)
	public Response toPath() {
		try (ManagersBundle mb = new ManagersBundle()) {
			Collection<Message> all = mb.mm.getAll();
			mb.commit();
			Document doc = XMLTransformer.toXML(all);
			Node result = XMLTransformer.toEventsOnly(doc);
			result = XMLTransformer.toKMLPath(result);
			return sendXML(result, "events_path.xml");
		} catch (RuntimeException | SQLException e) {
			return handleException(e);
		}
	}

	public static User getUserFromSession(Long sessionId, ManagersBundle mb) throws InvalidKeyException,
			SQLException {
		String userId = mb.lm.getSession(sessionId).getUser().getId();
		User user = mb.lm.get(userId);
		return user;
	}

	private static EventMessage getEvent(long eventId, ManagersBundle mb) throws InvalidKeyException,
			SQLException {
		Message msg = mb.mm.get(eventId);
		if (isEvent(msg) == false) {
			throw new InvalidKeyException(eventId);
		}
		EventMessage event = (EventMessage)msg;
		return event;
	}

	static JSONObject conToJson(Consensus con, ManagersBundle mb, User user) throws InvalidEventException,
			InvalidUserException, JSONException, InvalidConsensusException, SQLException {
		JSONObject result = new JSONObject();
		result.put("id", con.getId());
		result.put("name", con.getName());
		if ((user != null) && mb.rm.isUserRegisteredToEvent(user, con.getEvent())) {
			result.put("myVote", mb.rm.getVote(con, user));
		}
		result.put("currStatus", mb.rm.getConsensusStatus(con));
		result.put("numVotes", mb.rm.getAllVotes(con).size());
		result.put("event", con.getEvent().getId());
		return result;
	}
}
